﻿using System;
using System.Collections.Generic;
using System.Linq;
using Commands.Logging;
using Commands.Scheduling;
using Commands.Settings;
using Commands.TorrentDownloaders;
using Commands.Commands;
using System.Threading.Tasks;
using System.Threading;

namespace Commands
{
    /// <summary>
    /// Class, which receive and execute commands.
    /// </summary>
    public class Executor : ICommandManager
    {
        #region Constructors and initialization

        /// <summary>
        /// Default constructor. 
        /// Executor inited with values from config file.
        /// </summary>
        public Executor()
        {
            _InitSchedule(SettingsHelper.GetSchedule());

            _InitExecutor(SettingsHelper.Settings.MailCredentials,
                SettingsHelper.Settings.DownloadFolder,
                SettingsHelper.Settings.TimeSettings.ShouldProgramAutostart);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="credentials">Credentials to get mail from server.</param>
        public Executor(MailSettings credentials)
        {
            _InitExecutor(credentials, SettingsHelper.Settings.DownloadFolder);
        }

        /// <summary>
        /// Create new Executor object and start timer.
        /// </summary>
        /// <param name="credentials">Credentials to get mail from server.</param>
        /// <param name="schedule">Schedule for updating.</param>
        /// <param name="folder">Path to default download folder.</param>
        public Executor(MailSettings credentials, ISchedule schedule, string downloadDir)
        {
            if (schedule == null)
                throw new ArgumentException();

            _InitExecutor(credentials, downloadDir);
            _InitSchedule(schedule);
        }

        /// <summary>
        /// Init executor. Method must be called by each class constructor.
        /// </summary>
        /// <param name="credentials">Credentials to get mail from server.</param>
        /// <param name="folder">Path to default download folder.</param>
        private void _InitExecutor(MailSettings credentials, string folder, bool shouldAutostart = false)
        {
            if (credentials == null)
                throw new ArgumentException();

            StartupManager.ShouldProgramAutostart(shouldAutostart);

            Log = new Log();
            FilesHelper.Init(folder);

            var mailer = new Mailer(credentials);
            var manager = new TorrentManager(SettingsHelper.Settings.Trackers, FilesHelper.DefaultDownloadDir);
            _methodManager = CommandsLoader.CreateMethodsManager(mailer, manager, this, 
                SettingsHelper.Settings.PluginsPath);
            _commandsParser = new CommandStringParser(_methodManager);
            _commandsSource = new GetCommandsEmailAENet(credentials, _commandsParser);

            if (Schedule.CheckAtStart)
                GetAndExecute();
        }

        /// <summary>
        /// Init schedule.
        /// </summary>
        private void _InitSchedule(ISchedule schedule)
        {
            Schedule = schedule;
            _timersManager = new TimersManager(this);
        }

        #endregion

        #region Public members

        /// <summary>
        /// Log, which contains info about all executor's actions.
        /// </summary>
        public Log Log
        {
            get;
            private set;
        }

        public ISchedule Schedule
        {
            get;
            private set;
        }

        public HiddeSettings HiddeSettings
        {
            get
            {
                return SettingsHelper.Settings.HideSettings;
            }
        }

        public event Action TaskStarted;
        public event Action<Message> TaskUpdated;
        public event Action<TaskMessage, IEnumerable<Command>> TaskFinished;

        /// <summary>
        /// Check e-mail for commands, then execute them.
        /// </summary>
        /// <returns>'True' if commands was received.</returns>
        public bool GetAndExecute()
        {
            bool hasCommandsBeenReceived = false;
            bool result = false;
            do
            {
                hasCommandsBeenReceived = _GetAndExecute();
                if (hasCommandsBeenReceived)
                    result = true;
            }
            while (hasCommandsBeenReceived);

            return result;
        }

        /// <summary>
        /// Execute string with commands.
        /// </summary>
        /// <param name="commandString">String which contains commands.</param>
        public void Execute(string commandString)
        {
            foreach (var command in _commandsParser.ParseCommandsString(commandString))
                // Try to invoke command.
                _methodManager.TryInvokeCommand(command);
        }

        /// <summary>
        /// Suspend computer. It will wake up in next time defined in schedule.
        /// </summary>
        public void Suspend()
        {
            Suspender.SuspendNow(Schedule.GetWakeupTime(DateTime.Now));
        }

        #endregion

        #region Private Methods

        private bool _GetAndExecute()
        {
            lock (_lock)
            {
                // If we are already executing commands - don't do check.
                if (_inProgress)
                    return false;
                else
                    _inProgress = true;
            }

            _AddTask();

            // Get list of commands to execute.
            IEnumerable<Command> commandsToExecute = new List<Command>();
            try
            {
                commandsToExecute = _commandsSource.GetCommands();
                _AddMessage("{0} commands was read.", commandsToExecute.Count());
            }
            catch (Exception ex)
            {
                _AddMessage(ex.Message);
            }

            // Execute commands in parralell.
            Parallel.ForEach(commandsToExecute, command =>
                {
                    // Try to execute command.
                    _methodManager.TryInvokeCommand(command);

                    // Log results.
                    _LogCommandResult(command);
                });

            _TaskFinished(Log.GetLastTask(), commandsToExecute);

            lock (_lock)
                _inProgress = false;

            return commandsToExecute.Count() > 0;
        }

        private object _lock = new object();

        private void _LogCommandResult(Command command)
        {
            _AddMessage("Command {0}, {1} and parameters:", command.Name,
                command.Succeeded ? "successful" : "failed");
            foreach (var parameter in command.Parameters)
                _AddMessage("Parameter {0}, value {1}", parameter.Name, parameter.Value);
            if (command.Exception != null)
                _AddMessage(command.Exception.ToString());
        }

        private void _AddTask()
        {
            Log.AddTask(@"Trying to get messages.");
            _FireTaskStarted();
            _FireTaskUpdated(Log.GetLastMessage());
        }

        private void _AddMessage(string message, params object[] parameters)
        {
            Log.AddMessage(message, parameters);
            _FireTaskUpdated(Log.GetLastMessage());
        }

        private void _TaskFinished(TaskMessage taskMessage, IEnumerable<Command> commands)
        {
            Log.AddMessage(@"Task finished.");
            _FireTaskUpdated(Log.GetLastMessage());

            if (TaskFinished != null)
                TaskFinished(taskMessage, commands);
        }

        private void _FireTaskUpdated(Message message)
        {
            if (TaskUpdated != null)
                TaskUpdated(message);
        }

        private void _FireTaskStarted()
        {
            if (TaskStarted != null)
                TaskStarted();
        }

        #endregion

        #region Private fields

        private IGetCommands _commandsSource;
        private MethodsManager _methodManager;
        private TimersManager _timersManager;
        private CommandStringParser _commandsParser;

        /// <summary>
        /// Flag showing that commands receiving\executing is in progress.
        /// </summary>
        private bool _inProgress;

        #endregion
    }
}
